﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Codehaus;
using Codehaus.Parsec;

namespace LaTeXGrapher
{
    using Scanner = Parser<_>;
    using Lexer = Parser<Tok>;
    using Lexeme = Parser<Tok[]>;
    using Binary = Map<double, double, double>;
    using Unary = Map<double, double>;
    using Term = Parser<object>;
    using Grammar = Parser<double>;
    using GrammarA = Parser<double[]>;
    using Expr = Double;
    using FunctionMap = IDictionary<string,Func<double[],double>>;

    public class ExpressionParser
    {
        IDictionary<string,double> _mapArguments = new Dictionary<string,double>();
        FunctionMap _mapUserFunctions = new Dictionary<string, Func<double[], double>>();
        Grammar _parser = null;
        static Grammar _parserSingle = null;
        static Grammar ParserSingle
        {
            get
            {
                if (_parserSingle == null)
                    _parserSingle = BuildParser(null, null);
                return _parserSingle;
            }
        }

        public ExpressionParser()
        {
            _parser = BuildParser(_mapUserFunctions, LookupCurrentArguments);
        }

        private double? LookupCurrentArguments(string s)
        {
            double d;
            if (_mapArguments != null && _mapArguments.TryGetValue(s, out d))
                return d;
            return null;
        }

        public double EvaluateWithArguments(string expression, IDictionary<string,double> arguments)
        {
            System.Diagnostics.Debug.WriteLine("Eval: " + expression);
            var temp = _mapArguments;
            _mapArguments = arguments;
            var res = Parsers.RunParser(expression, _parser, "calculator");
            _mapArguments = temp;
            return res;
        }

        public bool EvaluateConditionalWithArguments(string expression, IDictionary<string, double> arguments)
        {
            return MathHelpers.ToBool(EvaluateWithArguments(expression, arguments));
        }

        public static double Evaluate(string expression)
        {
            return Parsers.RunParser(expression, ParserSingle, "calculator");
        }

        public static bool EvaluateConditional(string expression)
        {
            // We might want to have separate parsing for conditionals verses normal
            // expressions, but this becomes hard with things like odd(n) being of type
            // double -> bool.  So for now we treat doubles as bools based on need with
            // x != 0 being true and x == 0 being false.
            return MathHelpers.ToBool(Parsers.RunParser(expression, ParserSingle, "calculator"));
        }

        public void ClearUserFunctions()
        {
            _mapUserFunctions.Clear();
        }

        public void SetUserFunction(string name, Func<double[], double> value)
        {
            _mapUserFunctions[name] = value;
        }

        static string[] Operators
        {
            get
            {
                return new string[] 
                { 
                    "+", "-", "*", "/", "^",
                    "(", ")", ",",
                    "!",  "|", "||", "&", "&&",
                    "=", "==", "!=", "<>", "<", ">", ">=", "<="
                };
            }
        }

        static Grammar BuildParser(FunctionMap map, Func<string, double?> argumentsMap)
        {
            // This is based mostly on the example on the NParsec website.

            // A lot of this could be much prettier.
            Pattern digits = Patterns.InRange('0', '9').Many(1);
            Pattern number = digits.Seq(Patterns.IsChar('.').Seq(digits).Optional()
                .Seq(Patterns.Any(Patterns.IsChar('e'), Patterns.IsChar('E'))
                .Seq(Patterns.Any(Patterns.IsChar('-'), Patterns.IsChar('+')).Optional().Seq(digits)).Optional()).Optional());
            Scanner s_number = Scanners.IsPattern("number", number, "number expected");
            Scanner s_word = Scanners.IsPattern("identifier", Patterns.IsWord().Seq(Patterns.IsChar('\'').Many()), "identifer expected");

            Scanner s_line_comment = Scanners.IsJavaLineComment();
            Scanner s_block_comment = Scanners.IsBlockComment("/*", "*/");
            Scanner s_whitespace = Scanners.IsWhitespaces();
            Lexer l_number = Lexers.Lex(s_number, Tokenizers.ForDecimal);
            Lexer l_keyword = Lexers.Lex(s_word, Tokenizers.ForWord);
            Terms ops = Terms.GetOperatorsInstance(Operators);
            Scanner s_delim = (s_line_comment | s_block_comment | s_whitespace).Many_();
            Lexer l_tok = ops.Lexer | l_number | l_keyword;
            Lexeme lexeme = Lexers.Lexeme(s_delim, l_tok).FollowedBy(Parsers.Eof());

            Binary plus = (a,b) => a + b;
            Binary minus = (a,b) => a - b;
            Binary mul = (a,b) => a * b;
            Binary div = (a,b) => a / b;
            Binary pow = Math.Pow;
            Unary neg = n => -n;

            Binary and = MathHelpers.AndDouble;
            Binary or = MathHelpers.OrDouble;
            Unary not = MathHelpers.NotDouble;

            Binary eq = (a, b) => MathHelpers.ToDouble(a == b);
            Binary neq = (a, b) => MathHelpers.ToDouble(a != b);
            Binary lt = (a, b) => MathHelpers.ToDouble(a < b);
            Binary gt = (a, b) => MathHelpers.ToDouble(a > b);
            Binary leq = (a, b) => MathHelpers.ToDouble(a <= b);
            Binary geq = (a, b) => MathHelpers.ToDouble(a >= b);

            Term p_binary_ops = ops.GetParser(Operators);

            Term p_whitespace_mul = p_binary_ops.Not().Cast<object>();
            Parser<Binary> p_plus = GetOperator(ops, "+", plus);
            Parser<Binary> p_minus = GetOperator(ops, "-", minus);
            Parser<Binary> p_mul = (ops.GetParser("*") | p_whitespace_mul).Seq(Parsers.Return(mul));
            Parser<Binary> p_div = GetOperator(ops, "/", div);
            Parser<Binary> p_pow = GetOperator(ops, "^", pow);
            Parser<Unary> p_neg = GetOperator(ops, "-", neg);

            Parser<Binary> p_and = GetOperator(ops, "&&", "&", and);
            Parser<Binary> p_or = GetOperator(ops, "||", "|", or);
            Parser<Unary> p_not = GetOperator(ops, "!", not);

            Parser<Binary> p_eq = GetOperator(ops, "==", "=", eq);
            Parser<Binary> p_neq = GetOperator(ops, "!=", "<>", neq);
            Parser<Binary> p_lt = GetOperator(ops, "<", lt);
            Parser<Binary> p_gt = GetOperator(ops, ">", gt);
            Parser<Binary> p_leq = GetOperator(ops, "<=", leq);
            Parser<Binary> p_geq = GetOperator(ops, ">=", geq);

            Term p_lparen = ops.GetParser("(");
            Term p_rparen = ops.GetParser(")");
            //Term p_lfunc = Terms.OnWord((f,l,s) => LookupFunction(s, map, argumentsMap)).Seq(p_lparen);

            Func<Stack<Func<double[], double>>, Term> memoFunc = a =>
                Terms.OnWord((f, l, s) => { a.Push(LookupFunction(s, map, argumentsMap)); return new object(); });

            Grammar p_number = Terms.OnDecimal<double>((f,l,s) => double.Parse(s));
            Grammar p_keyword = Terms.OnWord<double>((f,l,s) => LookupConstant(s, map, argumentsMap));

            Grammar[] lazy_expr = new Grammar[1];
            Grammar p_lazy_expr = Parsers.Lazy<Expr>(() => lazy_expr[0]);

            GrammarA[] lazy_func = new GrammarA[1];
            GrammarA p_lazy_func = Parsers.Lazy<double[]>(() => lazy_func[0]);

            var fmem = new Stack<Func<double[], double>>();
            Binder<double[], double> binder = p => Parsers.Return(fmem.Pop()(p));
            Grammar p_term = p_lazy_expr.Between(p_lparen, p_rparen)
                | memoFunc(fmem).Seq(p_lazy_func.Between(p_lparen, p_rparen) | Parsers.Return(new double[]{})).Bind<Expr>(binder)
                | p_keyword 
                | p_number;
            OperatorTable<Expr> optable = new OperatorTable<Expr>()
              .Infixl(p_eq, 5)
              .Infixl(p_neq, 5)
              .Infixl(p_lt, 5)
              .Infixl(p_gt, 5)
              .Infixl(p_leq, 5)
              .Infixl(p_geq, 5)
              .Infixl(p_and, 7)
              .Infixl(p_or, 7)
              .Infixl(p_plus, 10)
              .Infixl(p_minus, 10)
              .Infixl(p_mul, 20)
              .Infixl(p_div, 20)
              .Infixr(p_pow, 25)
              .Prefix(p_neg, 30)
              .Prefix(p_not, 30);
            Grammar p_expr = Expressions.BuildExpressionParser(p_term, optable);
            lazy_expr[0] = p_expr;

            var p_args = p_expr.SepBy(ops.GetParser(","));
            lazy_func[0] = p_args;
            
            return Parsers.ParseTokens(lexeme, p_expr.FollowedBy(Parsers.Eof()), "calculator");
        }

        static Parser<T> GetOperator<T>(Terms ops, string op, T v)
        {
            return ops.GetParser(op).Seq(Parsers.Return(v));
        }

        static Parser<T> GetOperator<T>(Terms ops, string op, string alt, T v)
        {
            return (ops.GetParser(op) | ops.GetParser(alt)).Seq(Parsers.Return(v));
        }

        static double LookupConstant(string s, FunctionMap map, Func<string,double?> arguments)
        {
            if (arguments != null)
            {
                double? a = arguments(s);
                if (a.HasValue)
                    return a.Value;
            }

            switch (s)
            {
                case "e":
                    return Math.E;
                case "pi":
                    return Math.PI;
                case "NaN":
                    return double.NaN;
            }

            Func<double[], double> f;
            if (map != null && map.TryGetValue(s, out f))
                return f(new double[]{}); // function with no arguments is a constant.

            throw new InvalidOperationException("Unknown keyword " + s);
        }

        static Func<double[], double> LookupFunction(string s, FunctionMap map, Func<string,double?> argumentsMap)
        {
            switch (s)
            {
                case "sin":
                    return MakeArray(Math.Sin);
                case "cos":
                    return MakeArray(Math.Cos);
                case "tan":
                    return MakeArray(Math.Tan);
                case "arctan":
                    return MakeArray(Math.Atan);
                case "arcsin":
                    return MakeArray(Math.Asin);
                case "arccos":
                    return MakeArray(Math.Acos);
                case "abs":
                    return MakeArray(Math.Abs);
                case "floor":
                    return MakeArray(Math.Floor);
                case "ceiling":
                    return MakeArray(Math.Ceiling);
                case "ln":
                    return MakeArray(Math.Log);
                case "log":
                    return MakeArray(Math.Log10);
                case "odd":
                    return MakeArray(MathHelpers.OddDouble);
                case "even":
                    return MakeArray(MathHelpers.EvenDouble);
            }

            Func<double[], double> f;
            if (map != null && map.TryGetValue(s, out f))
                return f;

            return a => LookupConstant(s, map, argumentsMap);
        }

        static Func<double[], double> MakeArray(Func<double, double> f)
        {
            return vs => f(vs[0]);
        }
    }
}
